local setmetatable = _G.setmetatable
local pairs = _G.pairs
local pcall = _G.pcall
local error = _G.error

local coroutine_wrap = coroutine.wrap
local coroutine_yield = coroutine.yield
local assert = assert

local global = _G

module "psync"

local node = {}
node.__index = node

function create_node()
	local self = setmetatable({}, node)
	self[1] = create_node_ud()
	self.serializers = {}
	self.message_queues = {}
	self._out_streams = {}
	self._in_streams = {}
	return self
end

function node:host(port)
	node_host(self[1], port)
end

function node:connect(addr, port)
	local link = node_connect(self[1], addr, port)
	self:process_events() -- Connect may generate messages
	return link
end

function node:disconnect()
	node_disconnect(self[1])
end

function node:accept_connections(f)
	node_accept_connections(self[1], f)
end

function node:auto_send_packets(f)
	node_auto_send_packets(self[1], f)
end

function node:send_packet_all()
	node_send_packet_all(self[1])
end

function node:in_stream(link)
	local str = self._in_streams[link]
	if not str then
		local ch = self:channel_bitstream(link)
		if not ch then return nil end

		str = {
			node = self,
			link = link,
			inp = true,
			int = function() return global.serialize.getint(ch) end,
			getfirst = function() return global.serialize.firstmsg(ch) end,
			getnext = function() return global.serialize.nextmsg(ch) end,
			print_state = function() global.serialize.print_state(ch) end
		}
		self._in_streams[link] = str
	end

	return str
end

function node:out_stream(link)
	local str = self._out_streams[link]
	if not str then
		local ch = self:channel_bitstream(link)
		if not ch then return nil end

		str = {
			node = self,
			link = link,
			outp = true,
			int = function(old)
				global.serialize.putint(ch, old)
				return old
			end,
			putend = function() global.serialize.putend(ch) end,
			print_state = function() global.serialize.print_state(ch) end
		}
		self._out_streams[link] = str
	end

	return str
end

function node:update()
	node_process(self[1])

	self:process_events()
	self:process_messages()
end

function node:latency(link)
	return node_link_latency(self[1], link)
end

function node:channel_bitstream(link)
	return self.serializers[link]
end

function node:process_messages()
	local queues = self.message_queues

	for i = 1,#queues do
		local queue = queues[i]
		local ret, err = pcall(self.message, self, queue)
		if not ret then
			-- TODO: Just drop the connection instead
			error("Error in connection " .. queue.link .. ": " .. err)
		end
	end
	
	return nil
end

function node:event(ev, data)
	-- Nothing by default
end

function node:channel_count()
	return 1
end

function node:process_events()
	while true do
		local ev, data = node_next_event(self[1])

		if ev == "link disconnect" then
			global.print("Link disconnect")
			-- Remove message queues belonging to this link
			local message_queues = self.message_queues
			local c = #message_queues
			local i = 1
			local link = data
			while i <= c do
				local v = message_queues[i]
				if v.link == link then
					global.print("Erased queue from slot " .. i)
					message_queues[i] = message_queues[c]
					message_queues[c] = nil
					c = c - 1
				else
					i = i + 1
				end
			end

			-- Remove all serializer references
			self.serializers[link] = nil
		elseif ev == "link created" then
			local link = data

			global.print(self, "Link " .. link .. " created")

			local serializer = node_extract_serializer(self[1], link)
			
			assert(serializer)

			self.serializers[link] = serializer

		elseif ev == "link connect" then
			local message_queues = self.message_queues
			local link = data
			
			local in_str = self:in_stream(link)

			global.print(self, "Link " .. link .. " connect")

			assert(in_str)
		
			message_queues[#message_queues + 1] = in_str

			-- Send bitstreams are created on request
		elseif ev == nil then
			break
		end

		self:event(ev, data)
	end
end
